/**
 * 
 */
package org.codehaus.groovy.grails.plugins.autodelegator

import grails.test.GrailsUnitTestCase

import java.lang.reflect.Method;

import org.codehaus.groovy.grails.plugins.autodelegator.delegator.DummyClassA
import org.codehaus.groovy.grails.plugins.autodelegator.delegator.ClassWithDeclaredFunction
import org.codehaus.groovy.grails.plugins.autodelegator.delegator.ClassWithDeclaredMethod
import org.codehaus.groovy.grails.plugins.autodelegator.delegator.ClassWithDelegateThatHasDeclaredMethod
import org.codehaus.groovy.grails.plugins.autodelegator.delegator.ClassWithDelegateThatHasDynamicMethod
import org.codehaus.groovy.grails.plugins.autodelegator.delegator.ClassWithDynamicMethod

/**
 * @author <a href="mailto:franz.see@gmail.com">See, Franz Allan Valencia</a>
 */
class DelegatorTest extends GrailsUnitTestCase {
	
	static final Class[] NO_ARGUMENTS = new Class[0];
	static final Object[] NO_PARAMETERS = new Object[0];
	
	def dummyObjectA;
	def nameOfDeclaredMethod;
	def nameOfPublicMethodDeclaredInParentClass;
	def nameOfOverridenMethod;
	def nameOfDynamicMethod;
	def nameOfNonExistentMethod;
	
	void setUp() {
		dummyObjectA = new DummyClassA();
		nameOfDeclaredMethod = 'methodOne'
		nameOfPublicMethodDeclaredInParentClass = 'toString'
		nameOfOverridenMethod = 'hashCode';
		nameOfDynamicMethod = 'newMethod';
		nameOfNonExistentMethod = 'nonExitentMethod';
		
		DummyClassA.metaClass."$nameOfDynamicMethod" = {};
	}
	
	void testDelegatedInvokeMethod_givenClassWithDeclaredFunctionThenInvokeDelcaredFunction() {
		ClassWithDeclaredFunction objectWithDeclaredFunction = new ClassWithDeclaredFunction();
		assertEquals 'declared method invovation', 'hello ClassWithDeclaredFunction', Delegator.delegatedInvokeMethod(objectWithDeclaredFunction, 'hello', NO_PARAMETERS);  
	}
	
	void testDelegatedInvokeMethod_givenClassWithDeclaredMethodThenInvokeDeclaredMethod() {
		ClassWithDeclaredMethod objectWithDeclaredMethod = new ClassWithDeclaredMethod();
		assertEquals 'declared method invovation', 'hello ClassWithDeclaredMethod', Delegator.delegatedInvokeMethod(objectWithDeclaredMethod, 'hello', NO_PARAMETERS);  
	}

	void testDelegatedInvokeMethod_givenClassWithDelegateThatHasDeclaredMethodThenInvokeDelcaredMethodOfDelegate() {
		ClassWithDelegateThatHasDeclaredMethod objectWithDelegateThatHasDeclaredMethod = new ClassWithDelegateThatHasDeclaredMethod();
		assertEquals 'delegated method invocation', 'hello ClassWithDeclaredMethod', Delegator.delegatedInvokeMethod(objectWithDelegateThatHasDeclaredMethod, 'hello', NO_PARAMETERS);
	}
	
	void testDelegatedInvokeMethod_givenClassWithDynamicMethodThenInvokeDynamicMethod() {
		ClassWithDynamicMethod objectWithDynamicMethod = new ClassWithDynamicMethod();
		assertEquals 'dynamic method invocation', 'hello ClassWithDynamicMethod', Delegator.delegatedInvokeMethod(objectWithDynamicMethod, 'hello', NO_PARAMETERS);
	}
	
	void testDelegatedInvokeMethod_givenClassWithDelegateThatHasDynamicMethodThenInvokeDynamicMethodOfDelegate() {
		ClassWithDelegateThatHasDynamicMethod objectWithDelegateThatHasDynamicMethod = new ClassWithDelegateThatHasDynamicMethod();
		assertEquals 'delegated dynamic method invocation', 'hello ClassWithDynamicMethod', Delegator.delegatedInvokeMethod(objectWithDelegateThatHasDynamicMethod, 'hello', NO_PARAMETERS);
	}
	
	void testGetDelegate_givenClassWithDelegateThenReturnDelegate() {
		ClassWithDelegateThatHasDeclaredMethod objectWithDelegateThatHasDeclaredMethod = new ClassWithDelegateThatHasDeclaredMethod();
		assertEquals 'Delegatee metaClass property', objectWithDelegateThatHasDeclaredMethod.classWithDeclaredMethod, Delegator.getDelegate(objectWithDelegateThatHasDeclaredMethod);
	}
	
	void testHasMetaClassMethod_givenClassWithDynamicMethodThenTrue() {
		ClassWithDynamicMethod objectWithDynamicMethod = new ClassWithDynamicMethod();
		assertTrue 'has delegatee metaClass property', Delegator.hasMetaClassMethod(objectWithDynamicMethod, 'hello');
	}
	
	void testDeriveClass(){
		Object[] objects = ["hello world", new Object(), new DummyClassA()];
		
		Class[] actualClasses = Delegator.deriveClasses(objects);
		
		assertEquals 'Classes', [String.class, Object.class, DummyClassA.class], actualClasses.toList();
	}
	
	void testGetDeclaredMethod_forDeclaredMethod() {
		assertGetDeclaredMethod 'NotNull', dummyObjectA, nameOfDeclaredMethod, NO_ARGUMENTS
	}
	
	void testGetDeclaredMethod_forPublicMethodDeclaredInParentClass() {
		assertGetDeclaredMethod 'Null', dummyObjectA, nameOfPublicMethodDeclaredInParentClass, NO_ARGUMENTS
	}
	
	void testGetDeclaredMethod_forOverridenMethod() {
		assertGetDeclaredMethod 'NotNull', dummyObjectA, nameOfOverridenMethod, NO_ARGUMENTS
	}
	
	void testGetDeclaredMethod_forDynamicMethod() {
		assertGetDeclaredMethod 'Null', dummyObjectA, nameOfDynamicMethod, NO_ARGUMENTS
	}
	
	void testGetDeclaredMethod_forNonExistentMethod() {
		assertGetDeclaredMethod 'Null', dummyObjectA, nameOfNonExistentMethod, NO_ARGUMENTS
	}
	
	void testGetMethod_forDelcaredMethod() {
		assertGetMethod 'Null', dummyObjectA, nameOfDeclaredMethod, NO_ARGUMENTS
	}
	
	void testGetMethod_forPublicMethodDeclaredInParentClass() {
		assertGetMethod 'NotNull', dummyObjectA, nameOfPublicMethodDeclaredInParentClass, NO_ARGUMENTS
	}
	
	void testGetMethod_forOverridenMethod() {
		assertGetMethod 'NotNull', dummyObjectA, nameOfOverridenMethod, NO_ARGUMENTS
	}
	
	void testGetMethod_forDynamicMethod() {
		assertGetMethod 'Null', dummyObjectA, nameOfDynamicMethod, NO_ARGUMENTS
	}
	
	void testGetMethod_forNonExistentMethod() {
		assertGetMethod 'Null', dummyObjectA, nameOfNonExistentMethod, NO_ARGUMENTS
	}
	
	void assertGetDeclaredMethod(String assertionType, Object owner, String methodName, Class[] paramterClasses) {
		Method actualMethod = Delegator.getDeclaredMethod(owner, methodName, paramterClasses);

		assertMethod(assertionType, owner, methodName, actualMethod);
	}

	void assertGetMethod(String assertionType, Object owner, String methodName, Class[] paramterClasses) {
		Method actualMethod = Delegator.getMethod(owner, methodName, paramterClasses);

		assertMethod(assertionType, owner, methodName, actualMethod);
	}

	void assertMethod(String assertionType, Object owner, String methodName, actualMethod) {
		String assertion = 'assert' + assertionType;
		this."$assertion" "${owner.class.name}#${methodName}()", actualMethod;
	}
}
